﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using UnityEngine;


#region 数据类型定义
public enum LogLevel
{
    LEVEL_INFO = 0,
    LEVEL_WARNING,
    LEVEL_ERROR
}
public enum CIFP
{
    AUTO = 0,
    YUYV422,
    MJPEG,
    NV12,
    BGR0
}
public class CameraDescription
{
    public bool supportYUYV422;
    public bool supportMJPEG;
    public bool supportNV12;
    public bool supportBGR0;
    public string desc;
}
public enum OpenModel
{
    URL = 0,
    Camera
}
public enum RtspTransportType
{
    UDP = 0,
    TCP
}

public enum OptionType
{
    OpenTimeout = 0,
    ReadTimeout,
    EnableVideo,
    EnableAudio,
    OpenModel,
    CameraWidth,
    CameraHeight,
    CameraFrameRate,
    CameraInputFormatPriority,
    NoBuffer,
    CodecForceLowDelay,
    OutputPixelformat,
    RTSPTransport,
}

public enum OutPixFmt
{
    PIX_FMT_AUTO = -1,

    PIX_FMT_YUV420P = 0,
    PIX_FMT_YUYV422 = 1,
    PIX_FMT_YUV422P = 4,
    PIX_FMT_YUV444P = 5,

    PIX_FMT_GRAY8 = 8,

    PIX_FMT_YUVJ420P = 12,
    PIX_FMT_YUVJ422P = 13,
    PIX_FMT_YUVJ444P = 14,

    PIX_FMT_NV12 = 23,
    PIX_FMT_NV21 = 24,

    PIX_FMT_RGB24 = 2,
    PIX_FMT_BGR24 = 3,

    PIX_FMT_ARGB = 25,
    PIX_FMT_RGBA = 26,
    PIX_FMT_ABGR = 27,
    PIX_FMT_BGRA = 28,
}

public enum FrameType
{
    VIDEO = 0,
    AUDIO = 1,
}
public enum FrameFlag
{
    NONE = 0,
    EOF = 1,
}
public enum CacheType
{
    Video = 0,
    Audio
}

public class Frame
{
    public Frame()
    {
        data = new IntPtr[8];
        linesize = new int[8];
    }
    public IntPtr ptr;
    public FrameType frameType;
    public int width;
    public int height;
    public int nb_samples;
    public int sample_rate;
    public IntPtr extended_data;
    public IntPtr[] data;
    public int[] linesize;
    public long pts;
    public int format;
    public FrameFlag flag;
    public double duration;
}

[StructLayout(LayoutKind.Sequential)]
public class PFCounts
{
    public int audioPktCount;
    public int videoPktCount;
    public int audioFrameCount;
    public int videoFrameCount;
};

public class VideoStream
{
    public IntPtr ptr;
    public int pixelwidth;
    public int pixelheight;
    public double fps;
    public int pixelfmt;
}
[StructLayout(LayoutKind.Sequential)]
public class AudioStream
{
    public int streamCount;
    public int samplerate;
    public int channels;
    public int channel_layout;
    public int samplefmt;
}

public class AudioPlayParams
{
    public AudioPlayParams()
    {
        desas = new AudioStream();
    }
    public AudioStream desas;
    public int hwSize;
}


public enum RetcodeType
{
    Unknow = -10,
    Success = 0,
    Alloc_Context_Failed = 10,
    FFmpeg_Log = 20,
    OpenTimeout = 30
}

public class StreamsParameters
{
    public RetcodeType retcode;
    public string errLog;
    public string describe;
    public VideoStream videoStream;
    public AudioStream audioStream;
}


public class ResampleData
{
    public int len;
    public IntPtr data;
}

public enum InterruptCode
{
    Uknow = 0,
    ReadTimeout,
    ForceQuit,
    EnqueueFailed,
    EndOfFile
}
#endregion

public class IStreamCapture
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void UnityAutoCheckLibrary()
    {
        CheckLibrary();
    }

    #region 函数接口
    protected const string moduleName = "StreamCapture.dll";

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    protected delegate void LogCallBackFuncPtr(int level, IntPtr log);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    protected delegate void OpenCallBackFuncPtr(IntPtr owner, int openIndex, IntPtr info);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    protected delegate void InterruptCallBackFuncPtr(IntPtr owner, int interruptCode);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void AudioPlayCallBack(IntPtr owner, IntPtr stream, int len);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void OpenAudioPlayCallBack(IntPtr owner, int openIndex, IntPtr audioPlayParam);

    #region 流捕获
    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern uint GetStreamCaptureVersion();

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void InitializeStreamCapture(LogCallBackFuncPtr cb);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void TerminateStreamCapture();

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern IntPtr CreateStreamCapture(IntPtr owner);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void DeleteStreamCapture(IntPtr capture);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void OpenAsync(IntPtr capture, int openIndex, byte[] url, OpenCallBackFuncPtr cb);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern IntPtr Open(IntPtr capture, byte[] url);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void RegisterInterruptCallback(IntPtr capture, InterruptCallBackFuncPtr cb);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void Close(IntPtr capture);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern IntPtr TryGrabFrame(IntPtr capture, int frameType);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern IntPtr TryGrabLastFrame(IntPtr capture, int frameType);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void RemoveFrame(IntPtr capture, int frameType);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern IntPtr GetPFCounts(IntPtr capture);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern double GetCurrentProgress(IntPtr capture);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern long GetDuration(IntPtr capture);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void Seek(IntPtr capture, double percent);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern int SetOption(IntPtr capture, int optionType, int value);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void ClearCache(IntPtr capture, int cacheType);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void SetAudioTrack(IntPtr capture, int index);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern IntPtr GetCameraInfo(byte[] cameraName);

    #endregion 流捕获

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern long GetTimestamp();

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern long GetTimestampUTC();

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr ArrayToPointer(Array array);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr MemorySet(IntPtr ptr, byte value, int len);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr MemoryAlignment(IntPtr srcdata, int width, int height, int linesize, byte[] destdata);

    #region 重采样
    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr CreateResampler();

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern void DeleteResampler(IntPtr sampler);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern int OpenResampler(IntPtr sampler, AudioStream srcas, AudioStream destas);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern int CloseResampler(IntPtr sampler);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr Resample(IntPtr sampler, IntPtr frame);
    #endregion 重采样

    #region 音频播放
    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr CreateAudioPlay(IntPtr owner);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    public static extern void DeleteAudioPlay(IntPtr ap);

    //[DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    //public static extern IntPtr OpenAudioPlay(IntPtr ap, IntPtr srcas);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void OpenAudioPlayAsync(IntPtr ap, int openIndex, AudioStream srcas, OpenAudioPlayCallBack opencb, AudioPlayCallBack playcb);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void StartAudioPlay(IntPtr ap);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void CloseAudioPlay(IntPtr ap);

    [DllImport(moduleName, CallingConvention = CallingConvention.Cdecl)]
    protected static extern void MixAudioFormat(IntPtr stream, byte[] src, int len, float volume);
    #endregion 音频播放

    #endregion 函数接口

    protected static LogCallBackFuncPtr logPtr = _LogCallback;
    protected static OpenCallBackFuncPtr openPtr = _OpenCallback;
    protected static InterruptCallBackFuncPtr interruptPtr = _InterruptCallback;
    public static AudioPlayCallBack audioPlayPtr = _AudioPlayCallBack;
    public static OpenAudioPlayCallBack openAudioPlayPtr = _OpenAudioPlayCallBack;

    private static void _LogCallback(int level, IntPtr log)
    {
        string msg = Marshal.PtrToStringAnsi(log);
        Dispatcher.Invoke(() => { StreamCapture.StreamCaptureLog((LogLevel)level, msg); });
    }

    public static void Init()
    {
        InitializeStreamCapture(logPtr);
    }
    public static void Deinit()
    {
        TerminateStreamCapture();
    }
    private static void _OpenCallback(IntPtr _owner, int openIndex, IntPtr _info)
    {
        IntPtr owner = _owner;
        IntPtr info = _info;
        Dispatcher.Invoke(() =>
        {
            try
            {
                StreamsParameters sp = new StreamsParameters();
                sp.retcode = (RetcodeType)Marshal.ReadInt32(info, 0);

                IntPtr errlogptr = Marshal.ReadIntPtr(info, 8);
                string errlog = Marshal.PtrToStringAnsi(errlogptr);
                sp.errLog = string.IsNullOrEmpty(errlog) ? "" : errlog;

                IntPtr describeptr = Marshal.ReadIntPtr(info, 16);
                string describe = Marshal.PtrToStringAnsi(describeptr);
                sp.describe = string.IsNullOrEmpty(describe) ? "" : describe;

                IntPtr vsptr = Marshal.ReadIntPtr(info, 24);
                IntPtr asptr = Marshal.ReadIntPtr(info, 32);
                if (sp.retcode == 0 && vsptr != IntPtr.Zero)
                {
                    VideoStream vs = new VideoStream();
                    ConvertVideoStream(ref vs, vsptr);
                    sp.videoStream = vs;
                }

                if (sp.retcode == 0 && asptr != IntPtr.Zero)
                {
                    AudioStream _as = new AudioStream();
                    ConvertAudioStream(ref _as, asptr);
                    AudioStream scas = new AudioStream();
                    scas = _as;
                    sp.audioStream = scas;
                }
                GCHandle handle = GCHandle.FromIntPtr(owner);
                IStreamCapture self = (IStreamCapture)handle.Target;
                if (self == null)
                {
                    Debug.LogError("Owner ptr is invaild!");
                    return;
                }
                self.OpenCallback(openIndex, sp);
            }
            catch (Exception ex)
            {
                Debug.LogWarning(ex);
            }
        });
    }

    protected virtual void OpenCallback(int openIndex, StreamsParameters sp) { }

    private static void _InterruptCallback(IntPtr _owner, int interruptCode)
    {
        IntPtr owner = _owner;
        Dispatcher.Invoke(() =>
        {
            try
            {
                GCHandle handle = GCHandle.FromIntPtr(owner);
                IStreamCapture self = (IStreamCapture)handle.Target;
                if (self == null)
                {
                    Debug.LogError("Owner ptr is invaild!");
                    return;
                }
                self.InterruptCallback((InterruptCode)interruptCode);
            }
            catch (Exception ex)
            {
                Debug.LogWarning(ex);
            }
        });
    }

    protected virtual void InterruptCallback(InterruptCode interruptCode) { }

    private static void _AudioPlayCallBack(IntPtr owner, IntPtr stream, int len)
    {
        GCHandle handle = GCHandle.FromIntPtr(owner);
        SCAudioPlay self = (SCAudioPlay)handle.Target;
        if (self == null)
        {
            Debug.LogError("Owner ptr is invaild!");
            return;
        }
        self.AudioPlayCallback(stream, len);
    }

    protected virtual void AudioPlayCallback(IntPtr stream, int len) { }

    private static void _OpenAudioPlayCallBack(IntPtr owner, int openIndex, IntPtr audioPlayParam)
    {
        Dispatcher.Invoke(() =>
        {
            AudioPlayParams app = null;
            if (audioPlayParam != IntPtr.Zero)
            {
                app = new AudioPlayParams();
                ConvertAudioPlayParams(ref app, audioPlayParam);
            }

            GCHandle handle = GCHandle.FromIntPtr(owner);
            SCAudioPlay self = (SCAudioPlay)handle.Target;
            if (self == null)
            {
                Debug.LogError("Owner ptr is invaild!");
                return;
            }
            self.OpenAudioPlayCallback(openIndex, app);
        });
    }

    protected virtual void OpenAudioPlayCallback(int openIndex, AudioPlayParams app) { }

    public static void CheckLibrary()
    {
        const string AVCODEC_DLL = "avcodec-58.dll";
        const string AVDEVICE_DLL = "avdevice-58.dll";
        const string AVFILTER_DLL = "avfilter-7.dll";
        const string AVFORMAT_DLL = "avformat-58.dll";
        const string AVUTIL_DLL = "avutil-56.dll";
        const string POSTPROC_DLL = "postproc-55.dll";
        const string SWRESAMPLE_DLL = "swresample-3.dll";
        const string SWSCALE_DLL = "swscale-5.dll";
        const string SDL2_DLL = "SDL2.dll";
        const string STREAMCAPTURE_DLL = "StreamCapture.dll";
        string path = Application.streamingAssetsPath + "/StreamCaptureAssets/";

        if (!System.IO.File.Exists(path + AVCODEC_DLL))
            Debug.LogError("Dll Not Found:" + AVCODEC_DLL);
        if (!System.IO.File.Exists(path + AVDEVICE_DLL))
            Debug.LogError("Dll Not Found:" + AVDEVICE_DLL);
        if (!System.IO.File.Exists(path + AVFILTER_DLL))
            Debug.LogError("Dll Not Found:" + AVFILTER_DLL);
        if (!System.IO.File.Exists(path + AVFORMAT_DLL))
            Debug.LogError("Dll Not Found:" + AVFORMAT_DLL);
        if (!System.IO.File.Exists(path + AVUTIL_DLL))
            Debug.LogError("Dll Not Found:" + AVUTIL_DLL);
        if (!System.IO.File.Exists(path + POSTPROC_DLL))
            Debug.LogError("Dll Not Found:" + POSTPROC_DLL);
        if (!System.IO.File.Exists(path + SWRESAMPLE_DLL))
            Debug.LogError("Dll Not Found:" + SWRESAMPLE_DLL);
        if (!System.IO.File.Exists(path + SWSCALE_DLL))
            Debug.LogError("Dll Not Found:" + SWSCALE_DLL);
        if (!System.IO.File.Exists(path + SDL2_DLL))
            Debug.LogError("Dll Not Found:" + SDL2_DLL);
        if (!System.IO.File.Exists(path + STREAMCAPTURE_DLL))
            Debug.LogError("Dll Not Found:" + STREAMCAPTURE_DLL);

        string workdir = System.IO.Directory.GetCurrentDirectory() + "/";

        if (!System.IO.File.Exists(workdir + AVCODEC_DLL))
        {
            System.IO.File.Copy(path + AVCODEC_DLL, workdir + AVCODEC_DLL);
            if (!System.IO.File.Exists(workdir + AVCODEC_DLL))
                Debug.LogError("Work Dir Dll Not Found:" + AVCODEC_DLL);
        }
        if (!System.IO.File.Exists(workdir + AVDEVICE_DLL))
        {
            System.IO.File.Copy(path + AVDEVICE_DLL, workdir + AVDEVICE_DLL);
            if (!System.IO.File.Exists(workdir + AVDEVICE_DLL))
                Debug.LogError("Work Dir Dll Not Found:" + AVDEVICE_DLL);
        }
        if (!System.IO.File.Exists(workdir + AVFILTER_DLL))
        {
            System.IO.File.Copy(path + AVFILTER_DLL, workdir + AVFILTER_DLL);
            if (!System.IO.File.Exists(workdir + AVFILTER_DLL))
                Debug.LogError("Work Dir Dll Not Found:" + AVFILTER_DLL);
        }
        if (!System.IO.File.Exists(workdir + AVFORMAT_DLL))
        {
            System.IO.File.Copy(path + AVFORMAT_DLL, workdir + AVFORMAT_DLL);
            if (!System.IO.File.Exists(workdir + AVFORMAT_DLL))
                Debug.LogError("Work Dir Dll Not Found:" + AVFORMAT_DLL);
        }
        if (!System.IO.File.Exists(workdir + AVUTIL_DLL))
        {
            System.IO.File.Copy(path + AVUTIL_DLL, workdir + AVUTIL_DLL);
            if (!System.IO.File.Exists(workdir + AVUTIL_DLL))
                Debug.LogError("Work Dir Dll Not Found:" + AVUTIL_DLL);
        }
        if (!System.IO.File.Exists(workdir + POSTPROC_DLL))
        {
            System.IO.File.Copy(path + POSTPROC_DLL, workdir + POSTPROC_DLL);
            if (!System.IO.File.Exists(workdir + POSTPROC_DLL))
                Debug.LogError("Work Dir Dll Not Found:" + POSTPROC_DLL);
        }
        if (!System.IO.File.Exists(workdir + SWRESAMPLE_DLL))
        {
            System.IO.File.Copy(path + SWRESAMPLE_DLL, workdir + SWRESAMPLE_DLL);
            if (!System.IO.File.Exists(workdir + SWRESAMPLE_DLL))
                Debug.LogError("Work Dir Dll Not Found:" + SWRESAMPLE_DLL);
        }
        if (!System.IO.File.Exists(workdir + SWSCALE_DLL))
        {
            System.IO.File.Copy(path + SWSCALE_DLL, workdir + SWSCALE_DLL);
            if (!System.IO.File.Exists(workdir + SWSCALE_DLL))
                Debug.LogError("Work Dir Dll Not Found:" + SWSCALE_DLL);
        }

        if (!System.IO.File.Exists(workdir + SDL2_DLL))
        {
            System.IO.File.Copy(path + SDL2_DLL, workdir + SDL2_DLL);
            if (!System.IO.File.Exists(workdir + SDL2_DLL))
                Debug.LogError("Work Dir Dll Not Found:" + SDL2_DLL);
        }

        if (!System.IO.File.Exists(workdir + STREAMCAPTURE_DLL))
        {
            System.IO.File.Copy(path + STREAMCAPTURE_DLL, workdir + STREAMCAPTURE_DLL);
            if (!System.IO.File.Exists(workdir + STREAMCAPTURE_DLL))
                Debug.LogError("Work Dir Dll Not Found:" + STREAMCAPTURE_DLL);
        }
    }

    public static void ConvertFrame(ref Frame frame, IntPtr ptr)
    {
        frame.ptr = ptr;
        frame.frameType = (FrameType)Marshal.ReadInt32(ptr, 0);
        frame.width = Marshal.ReadInt32(ptr, 4);
        frame.height = Marshal.ReadInt32(ptr, 8);
        frame.nb_samples = Marshal.ReadInt32(ptr, 12);
        frame.sample_rate = Marshal.ReadInt32(ptr, 16);
        frame.extended_data = Marshal.ReadIntPtr(ptr, 24);
        Marshal.Copy(new IntPtr(ptr.ToInt64() + 32), frame.data, 0, 8);
        Marshal.Copy(new IntPtr(ptr.ToInt64() + 96), frame.linesize, 0, 8);
        frame.pts = Marshal.ReadInt64(ptr, 128);
        frame.format = Marshal.ReadInt32(ptr, 136);
        frame.flag = (FrameFlag)Marshal.ReadInt32(ptr, 140);
        frame.duration = BitConverter.ToDouble(BitConverter.GetBytes(Marshal.ReadInt64(ptr, 144)), 0);
    }
    public static void ConvertVideoStream(ref VideoStream _vs, IntPtr ptr)
    {
        _vs.ptr = ptr;
        _vs.pixelwidth = Marshal.ReadInt32(ptr);
        _vs.pixelheight = Marshal.ReadInt32(ptr, 4);
        _vs.fps = BitConverter.ToDouble(BitConverter.GetBytes(Marshal.ReadInt64(ptr, 8)), 0);
        _vs.pixelfmt = Marshal.ReadInt32(ptr, 16);
    }
    public static void ConvertAudioStream(ref AudioStream _as, IntPtr ptr)
    {
        _as.streamCount = Marshal.ReadInt32(ptr);
        _as.samplerate = Marshal.ReadInt32(ptr, 4);
        _as.channels = Marshal.ReadInt32(ptr, 8);
        _as.channel_layout = Marshal.ReadInt32(ptr, 12);
        _as.samplefmt = Marshal.ReadInt32(ptr, 16);
    }
    public static void ConvertAudioPlayParams(ref AudioPlayParams _app, IntPtr ptr)
    {
        ConvertAudioStream(ref _app.desas, Marshal.ReadIntPtr(ptr));
        _app.hwSize = Marshal.ReadInt32(ptr, 8);
    }
    public static void ConvertResampleData(ref ResampleData rd, IntPtr ptr)
    {
        rd.len = Marshal.ReadInt32(ptr);
        rd.data = Marshal.ReadIntPtr(ptr, 8);
    }

    public static void ConvertPFCount(ref PFCounts pfCounts, IntPtr ptr)
    {
        pfCounts = Marshal.PtrToStructure<PFCounts>(ptr);
    }

    public static void ConvertCameraDescription(ref CameraDescription desc, IntPtr ptr)
    {
        desc.supportYUYV422 = Marshal.ReadInt32(ptr) == 0 ? false : true;
        desc.supportMJPEG = Marshal.ReadInt32(ptr, 4) == 0 ? false : true;
        desc.supportNV12 = Marshal.ReadInt32(ptr, 8) == 0 ? false : true;
        desc.supportBGR0 = Marshal.ReadInt32(ptr, 12) == 0 ? false : true;
        IntPtr descPtr = Marshal.ReadIntPtr(ptr, 16);
        desc.desc = Marshal.PtrToStringAnsi(descPtr);
    }
}